﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;

namespace ComputerArchitecture.Numbers
{
    public abstract class BitNumber : IBitNumber
    {
        #region Fields

        private readonly IList<Bit> _bits; 

        #endregion

        #region Properties

        public virtual bool IsZero
        {
            get { return _bits.All(b => !b.IsSet); }
        }

        public virtual Bit this[int index]
        {
            get { return _bits[index]; }
            set
            {
                ArgumentValidator.NullValidate(value);
                _bits[index] = value;
            }
        }

        public virtual int Size
        {
            get { return _bits.Count; }
        }

        protected IList<Bit> Bits
        {
            get { return _bits; }
        }

        #endregion

        #region Constructors

        protected BitNumber(int count)
        {
            if (count <= 0)
            {
                throw new ArgumentException("count can't be less or equal zero");
            }
            _bits = new List<Bit>(count);
            for (int i = 0; i < count; ++i)
            {
                _bits.Add(Bit.Zero);
            }
        }

        protected BitNumber(IEnumerable<Bit> bits)
        {
            // todo: if bits is empty
            if (ReferenceEquals(bits, null))
            {
                throw new ArgumentNullException("bits");
            }
            _bits = new List<Bit>();
            foreach (Bit bit in bits)
            {
                _bits.Add((Bit) bit.Clone());
            }
        }

        #endregion

        #region Methods

        public abstract void Expand(int newSize);

        public void Xor(IBitNumber other)
        {
            ArgumentValidator.NullValidate(other);
            if (this.Size != other.Size)
            {
                throw new ArgumentException();
            }
            Parallel.For(0, this.Size, i => this.Bits[i].Xor(other[i]));
        }

        public void And(IBitNumber other)
        {
            ArgumentValidator.NullValidate(other);
            if (this.Size != other.Size)
            {
                throw new ArgumentException();
            }
            Parallel.For(0, this.Size, i => this.Bits[i].And(other[i]));
        }

        public void Or(IBitNumber other)
        {
            ArgumentValidator.NullValidate(other);
            if (this.Size != other.Size)
            {
                throw new ArgumentException();
            }
            Parallel.For(0, this.Size, i => this.Bits[i].Or(other[i]));
        }

        public void Negate()
        {
            Parallel.For(0, this.Size, i => this.Bits[i].Not());
        }

        public void CopyTo(IBitNumber number, int startPosition, int offset, int count)
        {
            ArgumentValidator.NullValidate(number);
            if (number.Size < startPosition + count)
            {
                throw new ArgumentException("other.Size < startPosition + count");
            }
            if (this.Size < offset + count)
            {
                throw new ArgumentException("this.Size < offset + count");
            }
            for (int destIdx = startPosition, sourceIdx = offset; destIdx < startPosition + count; ++destIdx, ++sourceIdx)
            {
                number[destIdx].SetAs(this[sourceIdx]);
            }
        }

        public void LogicShiftLeft(int bitCount, out Bit carryBit)
        {
            int realBitCount = bitCount % this.Size;
            carryBit = (Bit)this[realBitCount - 1].Clone();
            for (int sourceBitIdx = realBitCount, destinationBitIdx = 0; sourceBitIdx < this.Size; ++sourceBitIdx, ++destinationBitIdx)
            {
                this[destinationBitIdx].SetAs(this[sourceBitIdx]);
                this[sourceBitIdx].Reset();
            }
        }

        public void LogicShiftRight(int bitCount, out Bit carryBit)
        {
            int realBitCount = bitCount % this.Size;
            carryBit = (Bit) this[this.Size - realBitCount].Clone();
            for (int sourceBitIdx = this.Size - realBitCount - 1, destinationBitIdx = this.Size - 1; sourceBitIdx >= 0; --sourceBitIdx, --destinationBitIdx)
            {
                this[destinationBitIdx].SetAs(this[sourceBitIdx]);
                this[sourceBitIdx].Reset();
            }
        }

        public void ArithmeticShiftLeft(int bitCount, out Bit carryBit)
        {
            this.LogicShiftLeft(bitCount, out carryBit);
        }

        public void ArithmeticShiftRight(int bitCount, out Bit carryBit)
        {
            int realBitCount = bitCount % this.Size;
            carryBit = (Bit)this[Size - realBitCount].Clone();
            Bit signBit = (Bit)this[0].Clone();
            for (int sourceBitIdx = this.Size - realBitCount - 1, destinationBitIdx = this.Size - 1; sourceBitIdx >= 0; --sourceBitIdx, --destinationBitIdx)
            {
                this[destinationBitIdx].SetAs(this[sourceBitIdx]);
                this[sourceBitIdx].SetAs(signBit);
            }
        }

        public void CycleShiftLeft(int bitCount)
        {
            throw new NotImplementedException();
        }

        public void CycleShiftRight(int bitCount)
        {
            throw new NotImplementedException();
        }

        public bool Equals(IBitNumber other)
        {
            if (ReferenceEquals(other, null))
            {
                return false;
            }
            if (other.Size != this.Size)
            {
                return false;
            }
            for (int i = 0; i < this.Size; ++i)
            {
                if (!_bits[i].Equals(other[i]))
                {
                    return false;
                }
            }
            return true;
        }

        public static bool Equals(IBitNumber first, IBitNumber second)
        {
            return first.Equals(second);
        }

        public abstract object Clone();
        
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null))
            {
                return false;
            }
            IBitNumber other = obj as IBitNumber;
            return !ReferenceEquals(other, null) && this.Equals(other);
        }

        public override int GetHashCode()
        {
            return (_bits != null ? _bits.GetHashCode() : 0);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public string ToString(string format)
        {
            return this.ToString(format, CultureInfo.CurrentCulture);
        }

        public string ToString(string format, IFormatProvider formatProvider)
        {
            // todo: regular expressions
            if (string.IsNullOrEmpty(format))
            {
                format = "g";
            }
            if (ReferenceEquals(formatProvider, null))
            {
                formatProvider = CultureInfo.CurrentCulture;
            }
            switch (char.ToUpperInvariant(format[0]))
            {
                case 'D':
                    int groupSize = int.Parse(format.Substring(1));
                    StringBuilder stringBuilder = new StringBuilder(this.Size * 2); 
                    for (int i = 0; i < this.Size; ++i)
                    {
                        if (i != 0 && i%groupSize == 0)
                        {
                            stringBuilder.Append(' ');
                        }
                        stringBuilder.Append(Bits[i].ToString());
                    }
                    return stringBuilder.ToString();
                case 'G':
                    return this.ToString();
                default:
                    throw new FormatException(String.Format("The {0} format string is not supported.", format));
            }
        }

        public IEnumerator<Bit> GetEnumerator()
        {
            return _bits.GetEnumerator();
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder(this.Size);
            foreach (var bit in _bits)
            {
                stringBuilder.Append(bit.ToString());
            }
            return stringBuilder.ToString();
        }

        #endregion
    }
}
